package net.alche.al.util.alches.classtool;

import java.util.Set;
import java.util.HashSet;
import java.util.Enumeration;
import java.net.URL;
import java.net.URLDecoder;
import java.util.jar.JarFile;
import java.net.JarURLConnection;
import java.util.jar.JarEntry;
import java.io.IOException;
import java.io.File;
import java.lang.annotation.Annotation;

public class Scanner {

  //
  // 从包package中获取所有的Class
  //
  // @param packageName
  // @return
  //
  public Set<Class<?>> getClasses(String packageName, boolean isDeep) throws Exception {
    Set<Class<?>> classes = new HashSet<>();
    boolean recursive = true;
    String packageDirName = packageName.replace('.', '/');
    Enumeration<URL> dirs;
    try {
      dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
      while (dirs.hasMoreElements()) {
        URL url = dirs.nextElement();
        String protocol = url.getProtocol();
        if ("file".equals(protocol)) {
          String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
          addClass(classes, filePath, packageName, isDeep);
        } else if ("jar".equals(protocol)) {
          JarFile jar;
          try {
            jar = ((JarURLConnection) url.openConnection()).getJarFile();
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
              JarEntry entry = entries.nextElement();
              String name = entry.getName();
              if (name.charAt(0) == '/') {
                name = name.substring(1);
              }
              if (name.startsWith(packageDirName)) {
                int idx = name.lastIndexOf('/');
                if (idx != -1) {
                  packageName = name.substring(0, idx).replace('/', '.');
                }
                if ((idx != -1) || recursive) {
                  if (name.endsWith(".class") && !entry.isDirectory()) {
                    String className = name.substring(packageName.length() + 1, name.length() - 6);
                    try {
                      classes.add(Class.forName(packageName + '.' + className));
                    } catch (ClassNotFoundException e) {
                      System.out.println("no found class:" + packageName + '.' + className);
                      e.printStackTrace();
                    } catch (NoClassDefFoundError ex) {
                      System.out.println("no define class:" + packageName + '.' + className);
                      ex.printStackTrace();
                    }
                  }
                }
              }
            }
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return classes;
  }

  public void addClass(Set<Class<?>> classes, String filePath, String packageName, boolean isDeep) throws Exception {
    File[] files = new File(filePath)
        .listFiles(file -> (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory());
    assert files != null;
    for (File file : files) {
      String fileName = file.getName();
      if (file.isFile()) {
        String classsName = fileName.substring(0, fileName.lastIndexOf("."));
        if (!packageName.isEmpty()) {
          classsName = packageName + "." + classsName;
        }
        doAddClass(classes, classsName);
      } else if (isDeep) {
        addClass(classes, file.getPath(), packageName + "." + fileName, isDeep);
      }
    }
  }

  public void doAddClass(Set<Class<?>> classes, final String classsName) throws Exception {
    classes.add(Class.forName(classsName));
  }

  public <A extends Annotation> Set<Class<?>> getAnnotationClasses(String packageName, Class<A> annotationClass,
      boolean isDeep)
      throws Exception {
    Set<Class<?>> controllers = new HashSet<>();
    Set<Class<?>> clsList = getClasses(packageName, isDeep);
    if (clsList != null && clsList.size() > 0) {
      for (Class<?> cls : clsList) {
        if (cls.getAnnotation(annotationClass) != null) {
          controllers.add(cls);
        }
      }
    }
    return controllers;
  }

  // public <A extends Annotation> Set<Class<?>> getAnnotationClasses(String packageName, Class<A> annotationClass)
  //     throws Exception {
  //   Set<Class<?>> controllers = new HashSet<>();
  //   Set<Class<?>> clsList = getClasses(packageName, false);
  //   if (clsList != null && clsList.size() > 0) {
  //     for (Class<?> cls : clsList) {
  //       if (cls.getAnnotation(annotationClass) != null) {
  //         controllers.add(cls);
  //       }
  //     }
  //   }
  //   return controllers;
  // }
}